﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Data.SqlTypes;

using Noris.Schedule.Support;
using Noris.Schedule.Support.Data;
using Noris.Schedule.Support.Services;
using Noris.Schedule.Support.Sql;
using Noris.Schedule.Support.DataFace;
using Noris.Schedule.Support.Green;
using Noris.Schedule.Support.Core;

using Noris.Schedule.Planning.DataFace;
using System.Drawing;

namespace Noris.Schedule.Planning.ProcessData
{
	// Funkce pro předělání celého plánu na základě mapy plánu.
	/// <summary>
	/// Základna pro plánování materiálu i kapacit, na základě společného plánování toku materiálu přes existující kapacity
	/// </summary>
	public partial class PlanningDataCls : PlanningComponentCls, IDisposable
	{
		#region SPOUŠTĚNÍ METOD PRO PŘEPLÁNOVÁNÍ
		/// <summary>
		/// Metoda pro přeplánování všech položek plánu, podle parametrů.
		/// Plán jako celek se nepředělá, ale přesunou se termíny operací podle aktuálního stavu využití kapacit.
		/// </summary>
		public void InteractivePlanningRePlan(PlanningInteractiveRePlanArgs remakeArgs)
		{
			this._RunPlanningRePlan(remakeArgs);
		}
		/// <summary>
		/// Metoda pro přeplánování všech položek plánu, které nejsou fixované.
		/// Plán jako celek se nepředělá, ale přesunou se termíny operací podle aktuálního stavu využití kapacit.
		/// Metoda připraví argument typu PlanningInteractiveRePlanArgs pro přeplánování do kapacit dle KPJ, do minulosti, a spustí funkci Přetermínovat.
		/// </summary>
		private void _RunPlanningRePlanUnfixedToHistory()
		{
			PlanningInteractiveRePlanArgs args = new PlanningInteractiveRePlanArgs();
			args.CapacityLimit = LimitedCType.ByPUCsetting;
			args.RegisterUnfixedTimeDir = TimeRange.TimeDirection.ToHistory;
			this._RunPlanningRePlan(args);
		}
		/// <summary>
		/// Vlastní řídící procedura přeplánování termínů
		/// </summary>
		/// <param name="rePlanArgs"></param>
		private void _RunPlanningRePlan(PlanningInteractiveRePlanArgs rePlanArgs)
		{
			// Parametr uložím do mé instanční proměnné, protože jej budu potřebovat průběžně používat a hlavně doplňovat:
			this._InterRePlanArgs = rePlanArgs;
			this._DistilledAxisMap = AxisDistilleMap.DistillateAxisMapFrom(this.AxisHeap);

			// 1. Odregistrovat z kapacit všechny nefixované operace:
			this._RePlanUnregisterNonfixedTasks();

			// 2. Fixované položky zapsat do mapy jako hotové, k jejich aktuálnímu času:
			this._RePlanSetFinishToFixedItems();

			// 3. Postupně projít všechny nody mapy v daném směru, a otermínovat je:
			this._RePlanRegisterAllUnfixed(rePlanArgs.RegisterUnfixedTimeDir);

			this._InterRePlanArgs = null;
		}
		/// <summary>
		/// Najde v mapě nefixované tasky a všechny je odebere z kapacit
		/// </summary>
		private void _RePlanUnregisterNonfixedTasks()
		{
			List<AxisDistilleItemMap> nonFixedList = this._DistilledAxisMap.GetNonFixedItemList();
			foreach (AxisDistilleItemMap mapItem in nonFixedList)
			{
				mapItem.ItemState = AxisDistilleItemPlanState.Detached;          // Položka je odebraná ze systému
				if (mapItem.ItemType != AxisDistilleItemType.TaskC) continue;    // Osu S nebudeme odregistrovávat
				CapacityPlanWorkItemCls task = this.AxisHeap.FindTaskCItem(mapItem.ItemGID);
				this._TaskUnRegister(task);
				mapItem.ItemState = AxisDistilleItemPlanState.Detached;
			}
		}
		/// <summary>
		/// Do fixních položek mapy vepíše jejich aktuální čas jako čas finální
		/// </summary>
		private void _RePlanSetFinishToFixedItems()
		{
			List<AxisDistilleItemMap> fixedList = this._DistilledAxisMap.GetFixedItemList();
			foreach (AxisDistilleItemMap fixedItem in fixedList)
			{
				fixedItem.TimeFinished = fixedItem.TimeOriginal;
			}
		}
		/// <summary>
		/// Metoda postupně vyhledává všechny položky mapy, které je možno termínovat, provede to, a jde na další. Až jsou všechny.
		/// </summary>
		/// <param name="timeDir"></param>
		private void _RePlanRegisterAllUnfixed(TimeRange.TimeDirection timeDir)
		{
			this._DistilledAxisMap.PrepareForSequentialRegister(timeDir);
			while (true)
			{
				// Požádám mapu, ať mi vydá další prvek ke zpracování (v připraveném směru):
				AxisDistilleItemMap mapItem = this._DistilledAxisMap.GetNextItemForRegister();
				if (mapItem == null) break;

				// Podle druhu položky ji otermínuji, na základě termínu dokončení předešlých operací:
				switch (mapItem.ItemType)
				{
					case AxisDistilleItemType.AxisS:
						this._RePlanSetTimingAxisS(mapItem);
						break;
					case AxisDistilleItemType.TaskC:
						this._RePlanSetTimingTaskC(mapItem);
						break;
				}
			}
		}
		/// <summary>
		/// Korektně otermínuje záznam osy S
		/// </summary>
		/// <param name="mapItem"></param>
		private void _RePlanSetTimingAxisS(AxisDistilleItemMap mapItem)
		{
			// Najdu záznam osy S:
			MaterialPlanAxisItemCls axis = this.AxisHeap.FindAxisSItem(mapItem.ItemGID);

			// Parametry procesu:
			PlanningInteractiveRePlanArgs rePlanArgs = this._InterRePlanArgs;
			TimeRange.TimeDirection timeDir = rePlanArgs.RegisterUnfixedTimeDir;

			// Najdu čas zahájení přeplánování (=Min nebo Max z předchozích finalizovaných položek):
			bool hasPrevItems;
			DateTime startTime = mapItem.GetStartTimeFromPrevItems(timeDir, !this._DistilledAxisMap.SequentForce, out hasPrevItems);
			if (startTime == DateTime.MinValue)
				startTime = this._RePlanGetStartTimingAxisS(axis, mapItem, hasPrevItems);
			if (startTime == DateTime.MinValue)
				return;                          // Bez určení času fakt nelze termínovat osu S.

			// Zajistím otermínování záznamu osy S:
			this._AxisTiming(axis, startTime, timeDir);

			// Vložím nový čas do položky mapy, zajistím optimalizaci chodu v mapě:
			TimeRange axisTime = axis.DatePlanTime;
			this._DistilledAxisMap.SequentSetFinalTimeRange(mapItem, axisTime);
		}
		/// <summary>
		/// Metoda zjistí, zda můžeme termínovat daný task bez určení času z předešlých úkolů.
		/// Pokud ano, vrátí datum počátku termínování.
		/// </summary>
		/// <param name="axis">Záznam osy S k termínování</param>
		/// <param name="mapItem"></param>
		/// <param name="hasPrevItems"></param>
		/// <returns></returns>
		private DateTime _RePlanGetStartTimingAxisS(MaterialPlanAxisItemCls axis, AxisDistilleItemMap mapItem, bool hasPrevItems)
		{
			// Parametry procesu:
			PlanningInteractiveRePlanArgs rePlanArgs = this._InterRePlanArgs;
			TimeRange.TimeDirection timeDir = rePlanArgs.RegisterUnfixedTimeDir;

			// Záznam osy nemá žádné předchůdce? Budeme jej termínovat od konce nebo od začátku:
			if (!hasPrevItems)
			{
				switch (timeDir)
				{
					case TimeRange.TimeDirection.ToHistory:
                        if (axis.DateFinalRequest.HasValue)
							return axis.DateFinalRequest.Value;  // Zde je naplněna hodnota data z poptávky = kdy se má finál vydat
						return axis.DatePlanEnd;                 // Poptávka neurčila datum výdeje, a nemáme předchůdce = určíme datum konce tak, jak bylo předtím.
					case TimeRange.TimeDirection.ToFuture:
						return rePlanArgs.DateBeginToFuture;     // Bez předchůdců, směr ToFuture = začne se pokud možno ihned.
				}
				// Není určen směr času, to nenastane (kontroly jsou výšeú, v klidu vrátím MinValue:
				return DateTime.MinValue;
			}

			// Pokud mám předchůdce, ale žádný z nich neurčil začátek:
			// a) jsem v první vlně, kdy hledám korektně => počkáme si, třeba příště bude líp 
			//     (viz metoda AxisDistilleMap.GetNextItemForRegister(), která se snaží vyřešit co jde korektní cestou):
			if (!this._DistilledAxisMap.SequentForce)
				return DateTime.MinValue;

			// b) jsem v druhé vlně (this._DistilledAxisMap.SequentForce je true), musím něco vrátit !!!
			//    Vrátím původní datum (ToHistory) nebo počáteční datum (ToFuture):
			switch (timeDir)
			{
				case TimeRange.TimeDirection.ToHistory:
					return axis.DatePlanEnd;               // Poptávka neurčila datum výdeje, a nemáme předchůdce = určíme datum konce tak, jak bylo předtím.
				case TimeRange.TimeDirection.ToFuture:
					return rePlanArgs.DateBeginToFuture;   // Bez předchůdců, směr ToFuture = začne se pokud možno ihned.
			}
			// Není určen směr času, to nenastane (kontroly jsou výšeú, v klidu vrátím MinValue:
			return DateTime.MinValue;
		}
		/// <summary>
		/// Korektně otermínuje záznam kapacitního tasku
		/// </summary>
		/// <param name="mapItem"></param>
		private void _RePlanSetTimingTaskC(AxisDistilleItemMap mapItem)
		{
			// Najdu task:
			CapacityPlanWorkItemCls task = this.AxisHeap.FindTaskCItem(mapItem.ItemGID);
			if (task == null)
				Throw.SysError(MessageInfo.Get("Chyba: při přeplánování nebyl nalezen požadovaný task %0.", mapItem.ItemGID));

			// Parametry procesu:
			PlanningInteractiveRePlanArgs rePlanArgs = this._InterRePlanArgs;
			TimeRange.TimeDirection timeDir = rePlanArgs.RegisterUnfixedTimeDir;

			// Najdu čas zahájení přeplánování závislý na předchozích úkolech (=Min nebo Max z předchozích finalizovaných položek):
			bool hasPrevItems;
			DateTime startTime = mapItem.GetStartTimeFromPrevItems(timeDir, !this._DistilledAxisMap.SequentForce, out hasPrevItems);

			// Pokud se nepodařilo určit čas zahájení, zkusíme vyřešit zahájení "z čisté vody":
			if (startTime == DateTime.MinValue)
			{
				startTime = this._RePlanGetStartTimingTaskC(task, mapItem, hasPrevItems);
				if (startTime == DateTime.MinValue)
					return;                          // Bez určení času fakt nelze termínovat kapacitu.
			}

			// Otermínuji task podle daných časů:
			task.SetTiming(timeDir, OperationPhase.None, startTime, rePlanArgs.CapacityLimit, true);
			this._TaskRegister(task);

			// Vložím nový čas do položky mapy, zajistím optimalizaci chodu v mapě:
			TimeRange taskTime = task.TimeWork;
			this._DistilledAxisMap.SequentSetFinalTimeRange(mapItem, taskTime);
		}
		/// <summary>
		/// Metoda zjistí, zda můžeme termínovat daný task bez určení času z předešlých úkolů.
		/// Pokud ano, vrátí datum počátku termínování.
		/// </summary>
		/// <param name="task"></param>
		/// <param name="mapItem"></param>
		/// <param name="hasPrevItems"></param>
		/// <returns></returns>
		private DateTime _RePlanGetStartTimingTaskC(CapacityPlanWorkItemCls task, AxisDistilleItemMap mapItem, bool hasPrevItems)
		{
			// Parametry procesu:
			PlanningInteractiveRePlanArgs rePlanArgs = this._InterRePlanArgs;
			TimeRange.TimeDirection timeDir = rePlanArgs.RegisterUnfixedTimeDir;

			// Task nemá žádné předchůdce? Budeme jej termínovat od konce nebo od začátku:
			// Poznámka: pro směr ToHistory to nepadá v úvahu, protože každý task někam míří, do osy S o příjmu z výroby.
			// Pro směr ToFuture to může být pro tasky, které začínají zcela bez komponent ("vaří ze vzduchu").
			if (!hasPrevItems)
			{
				switch (timeDir)
				{
					case TimeRange.TimeDirection.ToHistory:
						return task.AxisData.DatePlanEnd;      // Tento případ nenastane, tohle je jen pojistka.
					case TimeRange.TimeDirection.ToFuture:
						return rePlanArgs.DateBeginToFuture;   // Bez předchůdců, směr ToFuture = začne se pokud možno ihned.
				}
				// Není určen směr času, to nenastane (kontroly jsou výšeú, v klidu vrátím MinValue:
				return DateTime.MinValue;
			}

			// Pokud mám předchůdce, ale žádný z nich neurčil začátek:
			// a) jsem v první vlně, kdy hledám korektně => počkáme si, třeba příště bude líp 
			//     (viz metoda AxisDistilleMap.GetNextItemForRegister(), která se snaží vyřešit co jde korektní cestou):
			if (!this._DistilledAxisMap.SequentForce)
				return DateTime.MinValue;

			// b) jsem v druhé vlně (this._DistilledAxisMap.SequentForce je true), musím něco vrátit !!!
			//    Vrátím původní datum (ToHistory) nebo počáteční datum (ToFuture):
			switch (timeDir)
			{
				case TimeRange.TimeDirection.ToHistory:
					return task.AxisData.DatePlanEnd;
				case TimeRange.TimeDirection.ToFuture:
					return rePlanArgs.DateBeginToFuture;   // Bez předchůdců, směr ToFuture = začne se pokud možno ihned.
			}
			// Není určen směr času, to nenastane (kontroly jsou výšeú, v klidu vrátím MinValue:
			return DateTime.MinValue;
		}
		#endregion
		#region PROMĚNNÉ A POMOCNÉ METODY
		private AxisDistilleMap _DistilledAxisMap;
		/// <summary>
		/// Argumenty pro řízení interaktivního přeplánování dat.
		/// Fyzicky se ukládají do obecného úložiště this._InterArgs.
		/// Zde jsou k dispozici přetypované na zdejší konkrétní typ (PlanningInteractiveRePlanArgs).
		/// </summary>
		private PlanningInteractiveRePlanArgs _InterRePlanArgs
		{
			get { return (PlanningInteractiveRePlanArgs)this._InterArgs; }
			set { this._InterArgs = value; }
		}
		#endregion
	}
	#region CLASS PlanningInteractiveMoveArgs : Data pro řízení interaktivního přeplánování
	/// <summary>
	/// Data pro řízení přeplánování celého plánu
	/// </summary>
	public class PlanningInteractiveRePlanArgs : PlanningInteractiveArgs
	{
		/// <summary>
		/// Vytvoří nový argument PlanningInteractiveRePlanArgs.
		/// Po jeho vytvoření je možno do něj zadat jednotlivé parametry procesu přeplánování.
		/// </summary>
		public PlanningInteractiveRePlanArgs()
		{
			this.CapacityLimit = LimitedCType.ByPUCsetting;
			this.RegisterUnfixedTimeDir = TimeRange.TimeDirection.ToHistory;
			this.DateBeginToFuture = PlanningParametersCls.Current.InitialDate;
		}
		/// <summary> { get; set; }
		/// Volba omezení kapacit při přesunu tasků.
		/// <para>Default je LimitedCType.ByPUCsetting = podle kapacitní jednotky (ta na sobě má svůj příznak omezené / neomezené kapacity).</para>
		/// </summary>
		public LimitedCType CapacityLimit { get; set; }
		/// <summary> { get; set; }
		/// Směr alokace kapacit nefixovaných tasků.
		/// <para>Default je TimeDirection.ToHistory = do minulosti.</para>
		/// </summary>
		public TimeRange.TimeDirection RegisterUnfixedTimeDir { get; set; }
		/// <summary> { get; set; }
		/// Datum počátku registrace při registraci ToFuture.
		/// <para>Default je PlanningControlCls.Current.InitialDate = spuštění procesu.</para>
		/// </summary>
		public DateTime DateBeginToFuture { get; set; }
	}
	#endregion
}
